package com.mingqijia.gassafety.authority.security.filter;

import com.gap.basic.base.CommonResponse;
import com.mingqijia.gassafety.authority.api.req.manager.ManagerLoginReqDTO;
import com.mingqijia.gassafety.authority.dal.helper.ConfigureHolder;
import com.mingqijia.gassafety.authority.security.user.SecurityUserLoginHolder;
import com.mingqijia.gassafety.authority.security.provider.AuthorityProvider;
import com.mingqijia.gassafety.authority.security.valueobject.ManagerVO;
import com.mingqijia.gassafety.authority.domain.adapter.BenAdapter;
import com.mingqijia.gassafety.shared.constant.Constants;
import com.mingqijia.gassafety.shared.enums.YesNoEnum;
import com.mingqijia.gassafety.shared.handler.GlobalAuthenticationException;
import com.mingqijia.gassafety.shared.jwt.JwtProvider;
import com.mingqijia.gassafety.shared.jwt.JwtToken;
import com.mingqijia.gassafety.shared.utils.ApplicationUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.*;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @author Damon S.
 * @version v1.0.1
 * @date 2020年07月30日 10:04
 */
@Slf4j
@Component
public class JwtLoginAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

    private final SecurityUserLoginHolder holder;
    private final AuthorityProvider aProvider;
    private final JwtProvider provider;
    private final ConfigureHolder cHolder;
    private final BenAdapter benAdapter;

    private static final String CAPTCHA = "captcha";

    public JwtLoginAuthenticationFilter(ConfigureHolder cHolder,
                                        JwtProvider provider,
                                        SecurityUserLoginHolder holder,
                                        BenAdapter benAdapter,
                                        AuthorityProvider aProvider) {
        this.cHolder = cHolder;
        this.provider = provider;
        this.holder = holder;
        this.benAdapter = benAdapter;
        this.aProvider = aProvider;
        setFilterProcessesUrl(Constants.TOKEN_URL);
    }

    @Autowired
    @Override
    public void setAuthenticationManager(AuthenticationManager manager) {
        super.setAuthenticationManager(manager);
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request,
                                                HttpServletResponse response) throws AuthenticationException {
        if (!HttpMethod.POST.name().equalsIgnoreCase(request.getMethod())) {
            throw new AuthenticationServiceException(
                    "Authentication method not supported: " + request.getMethod());
        }

        // 原参数形式
        if (!request.getContentType().contains(MediaType.APPLICATION_JSON_VALUE)) {
            // 校验验证码
            if (aProvider.checkCaptcha(obtainUsername(request), obtainCaptcha(request))) {
                throw new GlobalAuthenticationException("验证码错误");
            }
            return super.attemptAuthentication(request, response);
        }

        // JSON参数形式
        ManagerLoginReqDTO loginReq;
        try {
            loginReq = ApplicationUtils.toBean(request.getInputStream(), ManagerLoginReqDTO.class);
        } catch (Exception e) {
            log.error("login filter error.", e);
            throw new GlobalAuthenticationException("登录信息错误");
        }

        // 校验验证码
        if (aProvider.checkCaptcha(loginReq.getUsername(), loginReq.getCaptcha())) {
            throw new GlobalAuthenticationException("验证码错误");
        }
        UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
                loginReq.getUsername(), loginReq.getPassword());
        setDetails(request, token);

        return getAuthenticationManager().authenticate(token);
    }

    /**
     * 成功验证后调用的方法
     * 如果验证成功，就生成token并返回
     **/
    @Override
    protected void successfulAuthentication(HttpServletRequest request,
                                            HttpServletResponse response,
                                            FilterChain chain,
                                            Authentication auth) throws IOException, ServletException {
        ManagerVO backend = (ManagerVO) auth.getPrincipal();

        // 如果需要 强制更改密码
        String forceChangePwd = cHolder.getConfigByKey(
                Constants.CACHE_KEY_CHANGE_PWD
        );
        if (YesNoEnum.parse(forceChangePwd).isFlag() &&
                backend.getForceChangePwd().isFlag()) {
            ApplicationUtils.forceChangePwd(response);
            return;
        }

        // 生成token并返回调用
        JwtToken.JwtTokenBuilder builder = JwtToken.builder()
                .uniqueName(backend.getUsername());
        // token 失效时间配置
        String expiredInStr = cHolder.getConfigByKey(
                Constants.CACHE_KEY_TOKEN_EXPIRY
        );
        if (StringUtils.hasText(expiredInStr)) {
            builder.expiredIn(Integer.parseInt(expiredInStr));
        }
        String token = provider.generateToken(builder.build());

        // 更新登录时间。登录后将登出时间更新为毫秒，用于判断token过期
        holder.whenManagerLogin(backend);

        // 适配本安系统的用户登录
        benAdapter.login(backend.getUsername());

        ApplicationUtils.writeOutJson(response, CommonResponse.success(token));
    }

    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request,
                                              HttpServletResponse response,
                                              AuthenticationException failed) throws IOException, ServletException {
        String message = failed.getMessage();
        if (failed instanceof GlobalAuthenticationException) {
            if (((GlobalAuthenticationException) failed).isLogStack()) {
                log.error("安全认证异常", failed);
            }
        } else if (failed instanceof LockedException) {
            message = "账户已锁定/未激活";
        } else if (failed instanceof BadCredentialsException) {
            message = "用户名或密码错误";
        }
        ApplicationUtils.writeOutJson(response, CommonResponse.failure(message));
    }

    private String obtainCaptcha(HttpServletRequest request) {
        return String.valueOf(request.getParameter(CAPTCHA));
    }
}
